From 396acf3bbbe00a192cb0ea0a9ccf91b1d8d2850b Mon Sep 17 00:00:00 2001
From: Fuwn <50817549+Fuwn@users.noreply.github.com>
Date: Sat, 24 Jan 2026 13:09:50 +0000
Subject: Initial commit
Created from https://vercel.com/new
---
.../websites/[websiteId]/realtime/RealtimeLog.tsx | 206 +++++++++++++++++++++
1 file changed, 206 insertions(+)
create mode 100644 src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx
(limited to 'src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx')
diff --git a/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx b/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx
new file mode 100644
index 0000000..1076361
--- /dev/null
+++ b/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx
@@ -0,0 +1,206 @@
+import { Column, Heading, IconLabel, Row, SearchField, Text } from '@umami/react-zen';
+import Link from 'next/link';
+import { useMemo, useState } from 'react';
+import { FixedSizeList } from 'react-window';
+import { SessionModal } from '@/app/(main)/websites/[websiteId]/sessions/SessionModal';
+import { useFormat } from '@/components//hooks/useFormat';
+import { Avatar } from '@/components/common/Avatar';
+import { Empty } from '@/components/common/Empty';
+import {
+ useCountryNames,
+ useLocale,
+ useMessages,
+ useMobile,
+ useNavigation,
+ useTimezone,
+ useWebsite,
+} from '@/components/hooks';
+import { Eye, User } from '@/components/icons';
+import { FilterButtons } from '@/components/input/FilterButtons';
+import { Lightning } from '@/components/svg';
+import { BROWSERS, OS_NAMES } from '@/lib/constants';
+
+const TYPE_ALL = 'all';
+const TYPE_PAGEVIEW = 'pageview';
+const TYPE_SESSION = 'session';
+const TYPE_EVENT = 'event';
+
+const icons = {
+ [TYPE_PAGEVIEW]: ,
+ [TYPE_SESSION]: ,
+ [TYPE_EVENT]: ,
+};
+
+export function RealtimeLog({ data }: { data: any }) {
+ const website = useWebsite();
+ const [search, setSearch] = useState('');
+ const { formatMessage, labels, messages, FormattedMessage } = useMessages();
+ const { formatValue } = useFormat();
+ const { locale } = useLocale();
+ const { formatTimezoneDate } = useTimezone();
+ const { countryNames } = useCountryNames(locale);
+ const [filter, setFilter] = useState(TYPE_ALL);
+ const { updateParams } = useNavigation();
+ const { isPhone } = useMobile();
+
+ const buttons = [
+ {
+ label: formatMessage(labels.all),
+ id: TYPE_ALL,
+ },
+ {
+ label: formatMessage(labels.views),
+ id: TYPE_PAGEVIEW,
+ },
+ {
+ label: formatMessage(labels.visitors),
+ id: TYPE_SESSION,
+ },
+ {
+ label: formatMessage(labels.events),
+ id: TYPE_EVENT,
+ },
+ ];
+
+ const getTime = ({ createdAt, firstAt }) => formatTimezoneDate(firstAt || createdAt, 'pp');
+
+ const getIcon = ({ __type }) => icons[__type];
+
+ const getDetail = (log: {
+ __type: string;
+ eventName: string;
+ urlPath: string;
+ browser: string;
+ os: string;
+ country: string;
+ device: string;
+ }) => {
+ const { __type, eventName, urlPath, browser, os, country, device } = log;
+
+ if (__type === TYPE_EVENT) {
+ return (
+ {eventName || formatMessage(labels.unknown)},
+ url: (
+
+ {urlPath}
+
+ ),
+ }}
+ />
+ );
+ }
+
+ if (__type === TYPE_PAGEVIEW) {
+ return (
+
+ {urlPath}
+
+ );
+ }
+
+ if (__type === TYPE_SESSION) {
+ return (
+ {countryNames[country] || formatMessage(labels.unknown)},
+ browser: {BROWSERS[browser]},
+ os: {OS_NAMES[os] || os},
+ device: {formatMessage(labels[device] || labels.unknown)},
+ }}
+ />
+ );
+ }
+ };
+
+ const TableRow = ({ index, style }) => {
+ const row = logs[index];
+ return (
+
+
+
+
+
+
+
+ {getTime(row)}
+
+
+
+ {getDetail(row)}
+
+
+
+ );
+ };
+
+ const logs = useMemo(() => {
+ if (!data) {
+ return [];
+ }
+
+ let logs = data.events;
+
+ if (search) {
+ logs = logs.filter(({ eventName, urlPath, browser, os, country, device }) => {
+ return [
+ eventName,
+ urlPath,
+ os,
+ formatValue(browser, 'browser'),
+ formatValue(country, 'country'),
+ formatValue(device, 'device'),
+ ]
+ .filter(n => n)
+ .map(n => n.toLowerCase())
+ .join('')
+ .includes(search.toLowerCase());
+ });
+ }
+
+ if (filter !== TYPE_ALL) {
+ return logs.filter(({ __type }) => __type === filter);
+ }
+
+ return logs;
+ }, [data, filter, formatValue, search]);
+
+ return (
+
+ {formatMessage(labels.activity)}
+ {isPhone ? (
+ <>
+
+
+
+
+
+
+ >
+ ) : (
+
+
+
+
+ )}
+
+
+ {logs?.length === 0 && }
+ {logs?.length > 0 && (
+
+ {TableRow}
+
+ )}
+
+
+
+ );
+}
--
cgit v1.2.3